package br.com.saeng2D.superficie;

import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.io.File;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.filechooser.FileFilter;

import br.com.saeng2D.formulario.FormularioPrincipal;
import br.com.saeng2D.objeto.AbstratoObjeto;
import br.com.saeng2D.objeto.apoio.Apoio1;
import br.com.saeng2D.objeto.apoio.Apoio2;
import br.com.saeng2D.objeto.apoio.Apoio3;
import br.com.saeng2D.objeto.forca.ForcaCentralizada;
import br.com.saeng2D.objeto.forca.ForcaMomento;
import br.com.saeng2D.objeto.linha.Linha;
import br.com.saeng2D.objeto.ponto.Ponto;
import br.com.saeng2D.superficie.manipl.AbstratoManipulador;
import br.com.saeng2D.superficie.manipl.ManipuladorPadrao;
import br.com.saeng2D.util.PacoteButton;
import br.com.saeng2D.util.Util;
import br.com.saeng2D.xml.ManipuladorXML;

//SUPERFICIE PARA ARMAZENAMENTO E MANIPULACAO DE OBJETOS
public class Superficie extends JPanel implements MouseListener, MouseMotionListener {
	private static final long serialVersionUID = 1L;

	//Formulario principal da aplicacao
	private final FormularioPrincipal formularioPrincipal;

	//Tratador de eventos ocorridos na superficie
	private AbstratoManipulador manipulador;

	//Cache com os objetos armazenados
	private AbstratoObjeto[] objetos;
	
	//Indicador de alteracoes
	private boolean alterado;

	//Representante do xml
	private File arquivo;
	
	//Desenha a grade
	private Grade grade;
	
	//Construtor
	public Superficie(FormularioPrincipal formularioPrincipal) {
		this.formularioPrincipal = formularioPrincipal;
		setBackground(Util.getCor("cor_superficie"));
		manipulador = new ManipuladorPadrao(this);
		objetos = new AbstratoObjeto[0];
		grade = new Grade(this);
		registrarEvento();
	}

	public AbstratoObjeto[] getObjetos() {
		return objetos;
	}

	public int getQtdObjetos() {
		return objetos.length;
	}

	public AbstratoObjeto getObjetoSelecionado() {
		for(AbstratoObjeto objeto : objetos) {
			if(objeto.isSelecionado()) {
				return objeto;
			}
		}
		return null;
	}
	
	public AbstratoObjeto[] getObjetosSelecionados() {
		List<AbstratoObjeto> lista = new ArrayList<AbstratoObjeto>();
		for(AbstratoObjeto objeto : objetos) {
			if(objeto.isSelecionado()) {
				lista.add(objeto);
			}
			if(objeto instanceof Linha) {
				Linha linha = (Linha) objeto;
				Ponto ponto1 = linha.getPonto1();
				Ponto ponto2 = linha.getPonto2();
				if(ponto1.isSelecionado()) {
					lista.add(ponto1);
				} 
				if(ponto2.isSelecionado()) {
					lista.add(ponto2);
				} 
			}
		}
		return lista.toArray(new AbstratoObjeto[0]);
	}
	
	//Registrar os ouvintes de eventos
	private void registrarEvento() {
		addMouseListener(this);
		addMouseMotionListener(this);
	}
	
	//Todos os eventos sao delegados ao Manipulador
	public void mouseClicked(MouseEvent e) {
		manipulador.mouseClicked(e);
	}

	public void mouseEntered(MouseEvent e) {
		manipulador.mouseEntered(e);
	}

	public void mouseExited(MouseEvent e) {
		manipulador.mouseExited(e);
	}

	public void mousePressed(MouseEvent e) {
		manipulador.mousePressed(e);
	}

	public void mouseReleased(MouseEvent e) {
		manipulador.mouseReleased(e);
	}

	public void mouseDragged(MouseEvent e) {
		manipulador.mouseDragged(e);
	}

	public void mouseMoved(MouseEvent e) {
		manipulador.mouseMoved(e);
	}

	//Substituir o objeto manipulador de eventos
	//Se o chamador for um botao, o mesmo sera destacado
	public void setManipulador(AbstratoManipulador manipulador, JButton button) {
		if(formularioPrincipal.getPacoteButton() != null) {
			formularioPrincipal.getPacoteButton().restaurarCor();
		}
		if(button != null) {
			formularioPrincipal.setPacoteButton(new PacoteButton(button));
			button.setBackground(Util.getCor("cor_botao_destaque"));
		}
		AbstratoManipulador antigo = this.manipulador;
		this.manipulador.antesSubstituicao();
		this.manipulador = manipulador;
		antigo.aposSubstituicao();
		repaint();
	}
	
	//Liberar todos os objetos do cache
	public synchronized void limparSuperficie() {
		objetos = new AbstratoObjeto[0];
		superficieAlterada();
		repaint();
	}
	
	//Armazenar o objeto passado como parametro
	public synchronized void adicionarObjeto(AbstratoObjeto objeto) {
		if(objeto == null) {
			return;
		}
		AbstratoObjeto[] temp = objetos;
		objetos	= new AbstratoObjeto[temp.length + 1];
		System.arraycopy(temp, 0, objetos, 0, temp.length);
		objetos[objetos.length -1] = objeto;
		superficieAlterada();
		repaint();
	}
	
	//Excluir o objeto passado como parametro
	public synchronized void excluirObjeto(AbstratoObjeto objeto) {
		if(objeto == null) {
			return;
		}
		boolean liberado = false;
		for(int i=0; i<objetos.length; i++) {
			if(objetos[i] == objeto) {
				objeto.antesExclusao();
				objetos[i] = null;
				liberado = true;
				break;
			}
		}
		if(liberado) {
			int j = 0;
			AbstratoObjeto[] temp = new AbstratoObjeto[objetos.length - 1];
			for(int i=0; i<objetos.length; i++) {
				if(objetos[i] != null) {
					temp[j] = objetos[i];
					j++;
				}
			}
			objetos = temp;
			superficieAlterada();
			repaint();
		}
	}
	
	public void superficieAlterada() {
		alterado = true;
	}
	
	//Desenhar toda a superficie
	public void paint(Graphics g) {
		super.paint(g);
		Graphics2D g2 = (Graphics2D) g;
		g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		grade.desenhar(g2);
		for(AbstratoObjeto objeto : objetos) {	
			objeto.desenhar(g2);
		}
	}	
	
	public void setArquivo(File arquivo) {
		this.arquivo = arquivo;
		if(this.arquivo == null) {
			formularioPrincipal.setTitle("");
		} else {
			formularioPrincipal.setTitle(arquivo.getAbsolutePath());
		}
	}
		
	//Retorna uma copia deste objeto.
	//Alguns objetos contidos nesta superficie nao podem ser retornados
	public Superficie clonarParaDiagramaCorpoLivre() {
		Apoio1 apoio1 = null;
		Apoio2 apoio2 = null;
		Apoio3 apoio3 = null;
		for (AbstratoObjeto objeto : objetos) {
			 if(objeto instanceof Apoio3) {
				 apoio3 = (Apoio3) objeto;
			 } else if(objeto instanceof Apoio2) {
				 apoio2 = (Apoio2) objeto;
			 } else if(objeto instanceof Apoio1) {
				 apoio1 = (Apoio1) objeto;
			 }
		}
		if(apoio1 == null && apoio2 == null && apoio3 == null) {
			Util.mensagem(this, Util.getString("msg_nenhum_apoio_adicionado"));
			return null;
		}
		
		if(apoio1 != null && apoio2 == null) {
			Util.mensagem(this, Util.getString("msg_somente_apoio1_adicionado"));
			return null;
		}
		
		if(apoio2 != null && apoio1 == null) {
			Util.mensagem(this, Util.getString("msg_somente_apoio2_adicionado"));
			return null;
		}
		//--------------------

		Superficie superficieClone = new Superficie(formularioPrincipal);
		
		superficieClone.setMinimumSize(getMinimumSize());
		superficieClone.setPreferredSize(getPreferredSize());
		superficieClone.setMaximumSize(getMaximumSize());
		List<Ponto> listaPontoGeral = new ArrayList<Ponto>();
		
		for(AbstratoObjeto objeto : objetos) {
			if(objeto instanceof Linha) {
				Linha linha = (Linha) objeto;
				Linha linhaPlotada = (Linha) linha.prototipo();
				linhaPlotada.setPlotado(true);
				superficieClone.adicionarObjeto(linhaPlotada);
				listaPontoGeral.add(linhaPlotada.getPonto1());
				listaPontoGeral.add(linhaPlotada.getPonto2());
				if(linhaPlotada.getPontoCentral() != null) {
					listaPontoGeral.add(linhaPlotada.getPontoCentral());
				}
				Linha linhaMedidaParalela = linha.getLinhaMedidaParalela();
				superficieClone.adicionarObjeto(linhaMedidaParalela);
				superficieClone.adicionarObjeto(linha.getForcaCentralizada2());
			} else if(objeto instanceof ForcaCentralizada) {
				ForcaCentralizada forcaC = (ForcaCentralizada) objeto;
				superficieClone.adicionarObjeto(forcaC.prototipo());
			} else if(objeto instanceof ForcaMomento) {
				ForcaMomento forcaM = (ForcaMomento) objeto;
				superficieClone.adicionarObjeto(forcaM.prototipo());
			}
		}

		List<Ponto> listaPontoNumerar = new ArrayList<Ponto>();
		
		int numero = 1;
		Iterator<Ponto> iteratorPonto = listaPontoGeral.iterator();
		while(iteratorPonto.hasNext()) {
			Ponto ponto = iteratorPonto.next();
			if(!contem(ponto, listaPontoNumerar)) {
				listaPontoNumerar.add(ponto);
			}
		}

		for (Ponto ponto : listaPontoNumerar) {
			ponto.setNumero("" + numero++);
		}
		
		return superficieClone;
	}
	
	private boolean contem(Ponto p, List<Ponto> l) {
		Iterator<Ponto> iterator = l.iterator();
		while(iterator.hasNext()) {
			Ponto ponto = iterator.next();
			if(ponto.ehIgual(p)) {
				return true;
			}
		}
		return false;
	}
	
//	TODO
	//Salvar o arquivo xml
	public void salvar(boolean chamarNovo, boolean chamarAbrir) {
		if(arquivo != null) {
			Util.salvarArquivo(this, arquivo);
		} else {
			JFileChooser fileChooser = new JFileChooser();
			fileChooser.setFileSelectionMode(Util.getInt("modoSelecaoEscolherArquivoSalvar"));
			fileChooser.setFileFilter(new FileFilter() {
				public String getDescription() {
					return "SAENG2D";
				}
				public boolean accept(File pathname) {
					return pathname.getName().endsWith(".xml");
				}
			});
			int status = fileChooser.showSaveDialog(formularioPrincipal);
			while (status == JFileChooser.APPROVE_OPTION) {
				File file = fileChooser.getSelectedFile();
				if (file != null) {
					if(file.exists()) {
						int i = JOptionPane.showConfirmDialog(formularioPrincipal, Util.getString("msg_ja_existe") + "\r\nDESEJA SUBSCREVER?", Util.getString("label_atencao"), JOptionPane.OK_OPTION);
						if(i == JOptionPane.YES_OPTION) {
							Util.salvarArquivo(this, file);
							status = JFileChooser.CANCEL_OPTION;
						} else {
							fileChooser.setCurrentDirectory(file);
							status = fileChooser.showSaveDialog(formularioPrincipal);
						}
					} else {
						Util.salvarArquivo(this, file);
						status = JFileChooser.CANCEL_OPTION;
					}
				} else {
					status = JFileChooser.ERROR_OPTION;
				}
			}
		}
		if(chamarNovo) {
			alterado = false;
			novo();
		} else if(chamarAbrir) {
			alterado = false;
			abrir();
		}
	}
	
//	TODO
	//Criar novo arquivo ainda nao salvo
	public void novo() {
		if(alterado) {
			int status = confirmar();
			if(status == JOptionPane.YES_OPTION) {
				salvar(true, false);
			} else {
				limparSuperficie();
				setArquivo(null);
				alterado = false;
			}
		} else {
			limparSuperficie();
			setArquivo(null);
			alterado = false;
		}
		repaint();
	}
	
	public int confirmar() {
		return Util.confirmar(formularioPrincipal, Util.getString("msg_superficie_alterada") + "\r\n" + Util.getString("msg_superficie_alterada_confirmar"));
	}

//	TODO
	//Abrir um arquivo xml e recriar os objetos
	public void abrir() {
		JFileChooser fileChooser = new JFileChooser();
		fileChooser.setFileSelectionMode(Util.getInt("modoSelecaoEscolherArquivoAbrir"));
		fileChooser.setFileFilter(new FileFilter() {
			public String getDescription() {
				return "SAENG2D";
			}
			public boolean accept(File pathname) {
				return pathname.getName().endsWith(".xml");
			}
		});
		if(alterado) {
			int status = confirmar();
			if(status == JOptionPane.YES_OPTION) {
				salvar(false, true);
			} else {
				status = fileChooser.showOpenDialog(formularioPrincipal);
				if (status == JFileChooser.APPROVE_OPTION) {
					File file = fileChooser.getSelectedFile();
					if (file != null) {
						Util.abrirArquivo(this, file);
					}
				}
			}
		} else {
			int status = fileChooser.showOpenDialog(formularioPrincipal);
			if (status == JFileChooser.APPROVE_OPTION) {
				File file = fileChooser.getSelectedFile();
				if (file != null) {
					Util.abrirArquivo(this, file);
				}
			}
		}
		repaint();
	}
	
//	TODO
	//Salva o arquivo existente com outro nome ou outro diretorio
	public void salvarComo() {
		File file = getArquivo();
		boolean ehNovo = file == null;
		arquivo = null;
		salvar(false, false);
		if(!ehNovo && getArquivo() == null) {
			setArquivo(file);
		}
	}
	
//	TODO
	//Salvar objetos da superficie(arquivo xml)
	public void salvarXML(String prefixo, PrintWriter pw) {
		for(AbstratoObjeto objeto : objetos) {
			objeto.salvarXML(prefixo, pw);
		}
	}

//	TODO
	//Recuperar os objetos do arquivo xml
	public void recuperarObjeto(ManipuladorXML xml) {
		for(AbstratoObjeto objeto : xml.getObjetos()) {
			adicionarObjeto(objeto);
		}
	}

//	TODO
//	public void alterarGrade() {
//		if(getQtdObjetos() > 0) {
//			Util.mensagem(formularioPrincipal, Util.getString("msg_restricao_alterar_grade"));
//			return;
//		}
//		String s = JOptionPane.showInputDialog(formularioPrincipal, "Tamanho da grade", "" + grade.lado);
//		if(s == null || s.trim().length() == 0) {
//			return;
//		}
//		int novo = grade.lado;
//		try {
//			novo = Integer.parseInt(s);
//		} catch(Exception e) {
//			Util.mensagem(formularioPrincipal, Util.getString("label_valor_invalido"));
//			return;
//		}
//		if(novo != grade.lado) {
//			grade.lado = novo;
//			if(Util.getBoolean("acompanhar_alteracao_grade")) {
//				Constante.GRADE_SUPERFICIE = novo;
//			}
//			repaint();
//		}
//	}

	public AbstratoManipulador getManipulador() {
		return manipulador;
	}

	public void setManipulador(AbstratoManipulador manipulador) {
		this.manipulador = manipulador;
	}

	public Grade getGrade() {
		return grade;
	}

	public void setGrade(Grade grade) {
		this.grade = grade;
	}

	public boolean isAlterado() {
		return alterado;
	}

	public void setAlterado(boolean alterado) {
		this.alterado = alterado;
	}

	public File getArquivo() {
		return arquivo;
	}

	public FormularioPrincipal getFormularioPrincipal() {
		return formularioPrincipal;
	}

	public void setObjetos(AbstratoObjeto[] objetos) {
		this.objetos = objetos;
	}	
	
	public AbstratoObjeto getUltimoObjetoAdicionado() {
		if(objetos.length > 0) {
			return objetos[objetos.length - 1];
		}
		return null;
	} 
}